package com.mills.gamebase.dao.impl;

import java.util.ArrayList;
import java.util.Collection;
import java.util.List;

import org.apache.log4j.Logger;
import org.hibernate.SessionFactory;
import org.hibernate.criterion.Restrictions;
import org.springframework.dao.DataAccessException;
import org.springframework.transaction.annotation.Transactional;

import com.mills.gamebase.dao.IAbstractDAO;
import com.mills.gamebase.model.AbstractModel;
import com.mills.gamebase.service.MessageType;
import com.mills.gamebase.service.StatusMessage;

/**
 * Diese abstrakte Klasse implementiert die vorrausgesetzte Funktionalität aus dem Interface {@linkplain AbstractDAO} und
 * definiert weitere notwendige Methoden, welche in den DAOs implementiert werden müssen.
 * 
 * @author Shane
 *
 * @param <AbstractModel> Dieser Parameter muss der Modellklasse entsprechen, welche von der erbenden DAO verwaltet wird
 */
public abstract class AbstractDAO<Model extends AbstractModel> implements IAbstractDAO<Model> {

	/**
	 * Die Sessionfactory
	 */
	protected SessionFactory sessionFactory;
	/**
	 * Die Klasse der behandelten Entit�t
	 */
	protected Class<Model> entityClazz;
	/**
	 * Der Name der Entit�t als String
	 */
	protected String entityModelName = "! entity model name not set !";
	/**
	 * Die Default Sortierung, wenn Collections zur�ckgegeben werden
	 */
	protected String ordering = "";

	protected Logger logger = Logger.getLogger(AbstractDAO.class);

	@SuppressWarnings("unchecked")
	@Transactional
	public Model get(Long id, String... preloads) {
		if(id == null || id < 1) {
			logger.warn("Invalid id for " + entityModelName + "! ('" + id + "')");
			return null;
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Loading " + entityModelName + " with id='" + id + "'...");
		}

		Model entity = null;
		try {
			entity = (Model) sessionFactory.getCurrentSession()
						.createCriteria(entityClazz)
						.add(Restrictions.eq("id", id))
						.uniqueResult();
		} catch(DataAccessException dae) {
			logger.error("Exception caught loading " + entityModelName + " with id='" + id + "!", dae);
		}

		if(entity == null) {
			logger.warn("No " + entityClazz + " found with id='" + id + "'!");
			return null;
		}

		preload(entity, preloads);
		return entity;
	}

	@SuppressWarnings("unchecked")
	@Transactional
	public List<Model> getAll(String... preloads) {
		if (logger.isDebugEnabled()) {
			logger.debug("Loading all entities of " + entityModelName + "...");
		}

		List<Model> entities = new ArrayList<Model>();
		try {
			entities = sessionFactory.getCurrentSession().createQuery("select model from " + entityModelName + " as model " + ordering).list();
		} catch (DataAccessException dae) {
			logger.error("Exception caught loading all entities of " + entityModelName + "!", dae);
		}

		for(Model entity : entities) {
			preload(entity, preloads);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Successfully loaded " + entities.size() + " entities of " + entityModelName);
		}

		return entities;
	}

	public StatusMessage delete(Model entity) {
		if(entity == null) {
			logger.info(entityModelName + " is null");
			return new StatusMessage(true, "L�schen konnte leider nicht durchgef�hrt werden.");
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Deleting " + entityModelName + " with id='" + entity.getId() + "'...");
		}

		try {
			sessionFactory.getCurrentSession().delete(entity);
		} catch(DataAccessException dae) {
			logger.error("Exception caught deleting " + entityModelName + " with id='" + entity.getId() + "'!", dae);
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Deleting " + entityModelName + " with id='" + entity.getId() + " successful.");
		}

		return new StatusMessage("L�schen erfolgreich durchgef�hrt.");
	}

	@SuppressWarnings("unchecked")
	@Transactional
	public StatusMessage save(Model entity) {
		if(entity == null) {
			logger.info(entityModelName + " is null!");
			return new StatusMessage(true, "Speichern konnte leider nicht durchgef�hrt werden.");
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Saving/Updating " + entityModelName + " with id='" + entity.getId() + "'...");
		}

		// Wenn noch keine Version gesetzt ist, dann muss diese initialisiert werden
		if (entity.getVersion() == null) {
			entity.setVersion(new Long(1L));
		} 
		
		// Wenn bereits eine Version vergeben ist, dann muss diese gepr�ft werden
		else {
			List<Long> dbEntityVersion = null;
			
			// Die Versionsnummer der DB Repr�sentation der Entit�t laden
			try {
				dbEntityVersion = (List<Long>) sessionFactory.getCurrentSession().createQuery("select distinct e.version from " + entityModelName + " as e where e.id = " + entity.getId());
			} catch (Exception e) {
				logger.error("Exception caught loading version from database. ", e);
			}
			
			// Wenn die SQL Abfrage ein Ergebnis zur�ckgegeben hat,...
			if (dbEntityVersion != null && dbEntityVersion.size() > 0) {
				// ...dann muss dieser Wert mit der Version der Entit�t verglichen werden
				if (entity.getVersion().intValue() == dbEntityVersion.get(0).intValue()) {
					entity.setVersion(entity.getVersion() + 1L);
				} else {
					StatusMessage msg=new StatusMessage("Die Daten wurden zwischenzeitlich ge�ndert.");
					msg.setType(MessageType.WARN);
					return msg;
				}
			} else {
				// Aktuell wird einfach aktzeptiert, wenn die Version in der Entit�t bereits gesetzt ist,
				// obwohl in der Datenbank keine Entit�t mit der ID gefunden wurde. Der Grund hierf�r ist,
				// dass unter Umst�nden eine Implementierung von Entit�ts Kopien denkbar ist. Auf andere Weise
				// kann dieser Zustand eigentlich nicht erreicht werden, es sei denn die ID der Entit�t wird
				// verf�lscht, was allerdings auf andere Probleme hindeuten w�rde. Deswegen gibt es nur eine 
				// Warnung
				logger.warn("The entity version is already set but there was no entity found in the database. Either the ID was modified or the entity was copied.");
			}
		}
		
		// Die Entit�t speichern
		try {
			sessionFactory.getCurrentSession().saveOrUpdate(entity);
		} catch(DataAccessException dae) {
			logger.error("Exception caught saving/updating " + entityModelName + " with id='" + entity.getId() + "'! ", dae);
			return new StatusMessage(true, "Es ist ein Fehler beim Speichern der " + entityModelName + " Entität aufgetreten.");
		}

		if (logger.isDebugEnabled()) {
			logger.debug("Saving/Updating " + entityModelName + " with id='" + entity.getId() + "' succeeded.");
		}

		return new StatusMessage("Speichern erfolgreich durchgeführt.");
	}

	public void setSessionFactory(SessionFactory sessionFactory) {
		this.sessionFactory = sessionFactory;
	}

	/**
	 * Diese Methode sorgt daf�r, dass {@linkplain Collection}s welche in der entsprechenden Entit�t liegen
	 * durch die get Methoden der DAO gepreloaded werden k�nnen.
	 * 
	 * @param entity
	 * @param preloads
	 */
	protected abstract void preload(Model entity, String... preloads);

	public Class<Model> getEntityClazz() {
		return entityClazz;
	}
}
